using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Cassandraemon.Operators;
using Cassandraemon.FullText;
using Cassandraemon.Util;
using Apache.Cassandra;


namespace Cassandraemon.Builder
{


	class CassandraOperatorBuilder<T> : Cassandraemon.Util.ExpressionVisitor 
	{
		public CassandraOperatorCondition<T> Condition { get; set; }
		
		
		public CassandraOperatorBuilder (string keySpace, ConsistencyLevel consistencyLevel)
		{ 
			Condition = new CassandraOperatorCondition<T>();
			Condition.ConsistencyLevel = consistencyLevel;
		} 
		
		
		public CassandraOperatorCondition<T> Build(Expression expression)
		{
			base.Visit(expression);
			
			return Condition;
		}
		
		protected override Expression VisitMethodCall (MethodCallExpression m)
		{
			Visit(m.Arguments[0]);
			
			switch (m.Method.Name) {
			
				case "Where" :
					ParseMethodCallExpression(m);
					return m;
					
				case "CountColumn" :
					ParseMethodCallExpression(m);
					if(m.Method.IsGenericMethod)
						Condition.OperatorType = CassandraOperatorType.MultiGetCount;
					else
						Condition.OperatorType = CassandraOperatorType.GetCount;
					return m;
					
				case "FirstColumn" :	
					ParseMethodCallExpression(m);
					Condition.Count = 1;
					Condition.PostProcessor = x =>
						{
							IEnumerable<CassandraEntity<T>> entityList = x as IEnumerable<CassandraEntity<T>>;
							if(entityList == null) return entityList;
							foreach(var entity in entityList)
							{
								// If first column is nothing then throw exception
								if(entity.Data == null)
									throw new InvalidOperationException("Source sequence is empty.");
							}
							return entityList;
						};
					return m;
					
				case "FirstOrDefaultColumn" :
					ParseMethodCallExpression(m);
					Condition.Count = 1;
					return m;
					
				case "LastColumn" :
					ParseMethodCallExpression(m);
					Condition.Reverse = !Condition.Reverse;
					Condition.Count = 1;
					Condition.PostProcessor = x =>
						{
							IEnumerable<CassandraEntity<T>> entityList = x as IEnumerable<CassandraEntity<T>>;
							if(entityList == null) return entityList;
							foreach(var entity in entityList)
							{
								// If first column is nothing then throw exception
								if(entity.Data == null)
									throw new InvalidOperationException("Source sequence is empty.");
							}
							return entityList;
						};
					return m;
					
				case "LastOrDefaultColumn" :
					ParseMethodCallExpression(m);
					Condition.Reverse = !Condition.Reverse;
					Condition.Count = 1;
					return m;
					
				case "ReverseColumn" :
					if(Condition.IsSetReverse || Condition.IsSetCount)
						throw new InvalidOperationException("You should specify ReverseColumn before 'First', 'Last', 'Take'.");
					else
						Condition.Reverse = !Condition.Reverse;
					return m;
					
				case "TakeColumn" :
					Condition.Count = (int)((ConstantExpression)PartialEvaluator.Eval(m.Arguments[1])).Value;
					return m;
					
				case "Take" :
					Condition.KeyCount = (int)((ConstantExpression)PartialEvaluator.Eval(m.Arguments[1])).Value;
					Condition.PostProcessor = x =>
						{
							List<CassandraEntity<T>> entityList = x as List<CassandraEntity<T>>;
							if(entityList == null) return entityList;
							return entityList.Take(Condition.KeyCount).ToList();
						};
					return m;
					
				default:
					//return base.VisitMethodCall (m);
					//return m;
					throw new InvalidOperationException("You specify not supported extension method call.");
			}
		}


		private void ParseMethodCallExpression(MethodCallExpression expression)
		{
			if(expression.Arguments.Count > 1)
			{
//				if(Condition.IsSetKeySpace)
//				{
//					throw new InvalidOperationException("You can specify query condition from one method only.");
//				}
//				else
//				{
					if(expression.Arguments[1] is UnaryExpression &&
					  (expression.Arguments[1] as UnaryExpression).Operand is LambdaExpression)
					{
						UnaryExpression unaryExpression = (UnaryExpression)expression.Arguments[1];
						LambdaExpression lambdaExpression = (LambdaExpression)unaryExpression.Operand;
						ParseMethodCallArgumentExpression(lambdaExpression.Body);
					}
					else
					{
						throw new InvalidOperationException("You can specify only LambdaExpression for Method Argument.");
					}
//				}
			}
		}
		
		private void ParseMethodCallArgumentExpression(Expression expression)
		{
			if(expression is BinaryExpression)
			{
				BinaryExpression b = (BinaryExpression)expression;
				switch (b.NodeType) 
				{
				case ExpressionType.Equal :
				case ExpressionType.GreaterThanOrEqual :
				case ExpressionType.LessThanOrEqual :
				case ExpressionType.GreaterThan :
				case ExpressionType.LessThan :
					GetConditionFromBinaryExpression(b);
					break;
				case ExpressionType.AndAlso :
					ParseMethodCallArgumentExpression(b.Left);
					ParseMethodCallArgumentExpression(b.Right);
					break;
				default:
					throw new InvalidOperationException("You can use Equal or AndAlso or GreaterThanOrEqual or LessThanEqual or GreaterThan or LessThan or Call in query condition.");
				}
			}
			else if(expression is MethodCallExpression)
			{
				GetConditionFromMethodCallExpression((MethodCallExpression)expression);
			}
			else
			{
				throw new InvalidOperationException("You can specify BinaryExpression or MethodCallExpression for query condition.");
			}
		}
		
		
		private void GetConditionFromBinaryExpression(BinaryExpression expression)
		{
			string memberName = string.Empty;
			object columnName = string.Empty;
			object setValue = string.Empty;
			
			if(ExpressionChecker.IsCassandraEntityMemberAccess(expression.Left))
			{
				MemberExpression member = (MemberExpression)expression.Left;
				memberName = member.Member.Name;
				setValue = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
			}
			else if(ExpressionChecker.IsCassandraEntityMemberAccess(expression.Right))
			{
				MemberExpression member = (MemberExpression)expression.Right;
				memberName = member.Member.Name;
				setValue = Expression.Lambda(expression.Left).Compile().DynamicInvoke();
			}
			else if(ExpressionChecker.IsCassandraEntityIndexAccess(expression.Left))
			{
				MethodCallExpression method = (MethodCallExpression)expression.Left;
				memberName = ((MemberExpression)method.Object).Member.Name;
				columnName = Expression.Lambda(method.Arguments[0]).Compile().DynamicInvoke();
				setValue = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
			}
			else if(ExpressionChecker.IsCassandraEntityIndexAccess(expression.Right))
			{
				MethodCallExpression method = (MethodCallExpression)expression.Right;
				memberName = ((MemberExpression)method.Object).Member.Name;
				columnName = Expression.Lambda(method.Arguments[0]).Compile().DynamicInvoke();
				setValue = Expression.Lambda(expression.Left).Compile().DynamicInvoke();
			}
			
			if(expression.NodeType == ExpressionType.Equal)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "key" :
					Condition.Key = setValue.ToCassandraByte();
					break;
				case "columnfamily" :
					Condition.ColumnFamily = setValue.ToString();
					break;
				case "supercolumn" :
					Condition.SuperColumn = setValue.ToCassandraByte();
					break;
				case "column" :
					Condition.Column = setValue.ToCassandraByte();
					break;
				case "index" :
					Condition.EqIndexExpression[columnName.ToCassandraByte()] = setValue.ToCassandraByte();
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Key, ColumnFamily, SuperColumn, Columnin, Index Equal expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.GreaterThanOrEqual)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "key" :
					Condition.StartKey = setValue.ToCassandraByte();
					break;
				case "supercolumn" :
				case "column" :
					Condition.StartColumn = setValue.ToCassandraByte();
					break;
				case "index" :
					Condition.GteIndexExpression[columnName.ToCassandraByte()] = setValue.ToCassandraByte();
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Key, SuperColumn, Columnin, Index GreaterThanOrEqual expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.LessThanOrEqual)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "key" :
					Condition.FinishKey = setValue.ToCassandraByte();
					break;
				case "supercolumn" :
				case "column" :
					Condition.FinishColumn = setValue.ToCassandraByte();
					break;
				case "index" :
					Condition.LteIndexExpression[columnName.ToCassandraByte()] = setValue.ToCassandraByte();
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Key, SuperColumn, Columnin, Index LessThanOrEqual expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.GreaterThan)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "index" :
					Condition.GtIndexExpression[columnName.ToCassandraByte()] = setValue.ToCassandraByte();
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Index GreaterThan expression.");
				}
			}
			else if(expression.NodeType == ExpressionType.LessThan)
			{
				switch (memberName.ToLowerInvariant()) 
				{
				case "index" :
					Condition.LtIndexExpression[columnName.ToCassandraByte()] = setValue.ToCassandraByte();
					break;
				default:
					throw new InvalidOperationException("You can specify cassandra query parameter only. You should specify Index LessThan expression.");
				}
			}
			else
			{
				throw new InvalidOperationException("You can use Equal, GreaterThanOrEqual, LessThanOrEqual, GreaterThan, LessThan only in query condition operator.");
			}
		}
		
		
		private void GetConditionFromMethodCallExpression(MethodCallExpression expression)
		{
			var memberName = ((MemberExpression)expression.Arguments[0]).Member.Name;
			
			if(expression.Method.Name == "In")
			{
				if(expression.Arguments.Count() > 1)
				{
					Array value = (Array)Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					switch (memberName) 
					{
						case "Key" :
							Condition.Keys = BuilderUtil.ConvertArray(value, x => x.ToCassandraByte());
							break;
						case "SuperColumn" :
						case "Column" :
							Condition.Columns = BuilderUtil.ConvertArray(value, x => x.ToCassandraByte());
							break;
						default:
							throw new InvalidOperationException("You can use 'In' method belong [Key, SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify more than one arguments for 'In' method.");
				}
			}
			else if(expression.Method.Name == "GreaterThanOrEqual")
			{
				if(expression.Arguments.Count() == 2)
				{
					object value = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					switch (memberName) 
					{
						case "Key" :
							Condition.StartKey = value.ToCassandraByte();
							break;
						case "SuperColumn" :
						case "Column" :
							Condition.StartColumn = value.ToCassandraByte();
							break;
						default:
							throw new InvalidOperationException("You can use 'GreaterThanOrEqual' method belong [Key, SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify one argument for 'GreaterThanOrEqual' method.");
				}
			}
			else if(expression.Method.Name == "LessThanOrEqual")
			{
				if(expression.Arguments.Count() == 2)
				{
					object value = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					switch (memberName) 
					{
						case "Key" :
							Condition.FinishKey = value.ToCassandraByte();
							break;
						case "SuperColumn" :
						case "Column" :
							Condition.FinishColumn = value.ToCassandraByte();
							break;
						default:
							throw new InvalidOperationException("You can use 'LessThanOrEqual' method belong [Key, SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify one argument for 'LessThanOrEqual' method.");
				}
			}
			else if(expression.Method.Name == "Between")
			{
				if(expression.Arguments.Count() == 3)
				{
					object value1 = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					object value2 = Expression.Lambda(expression.Arguments[2]).Compile().DynamicInvoke();
					switch (memberName) 
					{
						case "Key" :
							byte[] key1 = value1.ToCassandraByte();
							byte[] key2 = value2.ToCassandraByte();
							if(key1.LessThanOrEqual(key2))
							{
								Condition.StartKey = key1;
								Condition.FinishKey = key2;
							}
							else
							{
								Condition.StartKey = key2;
								Condition.FinishKey = key1;
							}
							break;
						case "SuperColumn" :
						case "Column" :
							byte[] column1 = value1.ToCassandraByte();
							byte[] column2 = value2.ToCassandraByte();
							Condition.StartColumn = column1;
							Condition.FinishColumn = column2;
							break;
						default:
							throw new InvalidOperationException("You can use 'Between' method belong [Key, SuperColumn, Column] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify two argument for 'Between' method.");
				}
			}
			else if(expression.Method.Name == "Match")
			{
				if(expression.Arguments.Count() == 3)
				{
					IAnalyzer analyzer = (IAnalyzer)Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					string query = (string)Expression.Lambda(expression.Arguments[2]).Compile().DynamicInvoke();
					var parser = new QueryParser();
					if(!parser.Parse(query)) throw new InvalidOperationException("Invalid full text search query.");
					switch (memberName) 
					{
						case "Key" :
							Condition.OperatorType = CassandraOperatorType.FullTextSearch;
							Condition.Analyzer = analyzer;
							Condition.Matches = parser.Match.ToList();
							Condition.UnMatches = parser.UnMatch.ToList();
							break;
						default:
							throw new InvalidOperationException("You can use 'Match' method belong [Key] only in query condition.");
					}
				}
				else if(expression.Arguments.Count() == 4)
				{
					IAnalyzer analyzer = (IAnalyzer)Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke();
					string[] matchArray = (string[])Expression.Lambda(expression.Arguments[2]).Compile().DynamicInvoke();
					string[] unmatchArray = (string[])Expression.Lambda(expression.Arguments[3]).Compile().DynamicInvoke();
					switch (memberName) 
					{
						case "Key" :
							Condition.OperatorType = CassandraOperatorType.FullTextSearch;
							Condition.Analyzer = analyzer;
							Condition.Matches = matchArray.ToList();
							Condition.UnMatches = unmatchArray.ToList();
							break;
						default:
							throw new InvalidOperationException("You can use 'Match' method belong [Key] only in query condition.");
					}
				}
				else
				{
					throw new InvalidOperationException("You can specify two or three argument for 'Match' method.");
				}
			}
			else
			{
				throw new InvalidOperationException("You can use 'In, GreaterThanOrEqual, LessThanOrEqual, Between, Match' method in query condition.");
			}
		}
	}
}
